import Calculate from './calculate';
import Cookie from './cookie';

/* eslint-disable camelcase */
/* eslint-disable no-console */
/* eslint-disable no-magic-numbers */
let extensionMap = {
	/* ===== List ===== */
	$__utilityList() {
		return Object.keys(extensionMap);
	},
	/* ===== Number ===== */
	$toMoney(num) {
		return num.toFixed(2).replace(/(\d{1,3})(?=(?:\d{3})+(?:\.|$))/g, '$1,');
	},
	$plus(num1, num2) {
		try {
			return +new Calculate(num1).plus(num2);
		} catch(ex) {
			console.warn(ex);
			return NaN;
		}
	},
	$minus(num1, num2) {
		try {
			return +new Calculate(num1).minus(num2);
		} catch(ex) {
			console.warn(ex);
			return NaN;
		}
	},
	$multiply(num1, num2) {
		try {
			return +new Calculate(num1).multiply(num2);
		} catch(ex) {
			console.warn(ex);
			return NaN;
		}
	},
	$divide(num1, num2) {
		try {
			return +new Calculate(num1).divide(num2);
		} catch(ex) {
			console.warn(ex);
			return NaN;
		}
	},
	$remainder(num1, num2) {
		try {
			return +new Calculate(num1).remainder(num2);
		} catch(ex) {
			console.warn(ex);
			return NaN;
		}
	},
	$modulo(num1, num2) {
		try {
			return +new Calculate(num1).modulo(num2);
		} catch(ex) {
			console.warn(ex);
			return NaN;
		}
	},
	$compare(num1, num2) {
		let diff = 0;
		try {
			diff = +new Calculate(num1).minus(num2);
		} catch(ex) {
			console.warn('Result may NOT reliable.');
			diff = num1 - num2;
			diff = Math.abs(diff) - 0 >= Number.EPSILON ? diff : 0;
		}
		return diff > 0 && '>' || diff < 0 && '<' || diff === 0 && '=';
	},
	$eq(num1, num2) {
		return this.$compare(num1, num2) === '=';
	},
	$neq(num1, num2) {
		return this.$compare(num1, num2) !== '=';
	},
	$lt(num1, num2) {
		return this.$compare(num1, num2) === '<';
	},
	$gt(num1, num2) {
		return this.$compare(num1, num2) === '>';
	},
	$lte(num1, num2) {
		return this.$compare(num1, num2) !== '>';
	},
	$gte(num1, num2) {
		return this.$compare(num1, num2) !== '<';
	},
	$smaller(num1, num2) {
		return this.$lte(num1, num2) ? num1 : num2;
	},
	$larger(num1, num2) {
		return this.$gte(num1, num2) ? num1 : num2;
	},
	/* ===== String ===== */
	$toCodePointArray(str) {
		let codePointArr = [];
		let i = 0,
			len = str.length;
		for (; this.$lt(i, len);) {
			codePointArr.push(str.codePointAt(i));
			i += this.$gt(str.codePointAt(i), 0xFFFF) + 1;
		}
		return codePointArr;
	},
	$toUnicode(str) {
		return this.$toCodePointArray(str).map(item => `\\u{${item.toString(16)}}`).join('');
	},
	$toCamelCase(str) {
		return str.trim().replace(/^[A-Z]/, match => match.toLowerCase()).replace(/[\s_\-]+([a-z])/g, (match, $1) => $1.toUpperCase());
	},
	$stringLength(str) {
		return [...str].length;
	},
	$sliceString(str, start = 0, end) {
		return [...str].slice(start, end != null ? end : this.$stringLength(str)).join('');
	},
	$thousandSeparate(str) {
		return [...str].reverse().join('').replace(/(\d{3})(?=\d+$)/g, '$1,').split('').reverse().join('');
	},
	$getURLSearchParam(key, search = location.search) {
		return (new RegExp(`\\?(?:.+(?:&|=))?${key}=([^&#]*)(?:&|#|$)`).exec(search) || [])[1] || '';
	},
	$padString(str, char = '', len = 0, pos = 0, cut = false) {
		let strLen = this.$stringLength(str);
		if (this.$lte(pos, 0)) {
			pos = this.$plus(strLen, pos);
		}
		if (this.$lte(len, strLen)) {
			return cut ? !pos ? this.$sliceString(str, -len) : this.$sliceString(str, 0, len) : str;
		}
		return `${this.$sliceString(str, 0, pos)}${char.repeat(this.$minus(len, strLen))}${this.$sliceString(str, pos)}`;
	},
	$maskString(str, char = '', start = 0, end = 0) {
		let strLen = this.$stringLength(str);
		if (this.$lte(start, 0)) {
			start = this.$plus(strLen, start);
		}
		if (this.$lte(end, 0)) {
			end = this.$plus(strLen, end);
		}
		start = this.$smaller(this.$larger(start, 0), strLen);
		end = this.$smaller(this.$larger(end, 0), strLen);
		if (this.$gte(start, end)) {
			return str;
		}
		let charLen = this.$minus(end, start);
		return this.$spliceArray([...str], start, charLen, char.repeat(charLen)).join('');
	},
	$encodeHTML(str) {
		return str.replace(/&/g, '&amp;')
			.replace(/</g, '&lt;')
			.replace(/>/g, '&gt;')
			.replace(/ /g, '&nbsp;')
			.replace(/'/g, '&#39;')
			.replace(/"/g, '&quot;');
	},
	$decodeHTML(str) {
		return str.replace(/&amp;/g, '&')
			.replace(/&lt;/g, '<')
			.replace(/&gt;/g, '>')
			.replace(/&nbsp;/g, ' ')
			.replace(/&#39;/g, '\'')
			.replace(/&quot;/g, '"');
	},
	$toSafeString(tpl, ...args) {
		return tpl.slice(1).reduce((res, currVal, currIdx) => `${res}${args[currIdx].replace(/&/g, '&amp;')
			.replace(/</g, '&lt;')
			.replace(/>/g, '&gt;')}${currVal}`, tpl[0]);
	},
	$getCookie(key) {
		return Cookie.getCookie(key);
	},
	$setCookie(key, val, opt) {
		return Cookie.setCookie(key, val, opt);
	},
	$clearCookie() {
		return Cookie.clearCookie();
	},
	$getUserAgentClient(ua = navigator.userAgent) {
		let result = {};
		Object.entries({
			'iPad|iPhone|iPod': 'iOS',
			'Android': 'Android',
			'MicroMessenger': 'WeChat',
			'Windows [^P][^h][^o][^n][^e]': 'Windows',
			'Linux(?!.+Android)': 'Linux',
			'Macintosh': 'Mac',
			'Windows Phone': 'Windows Phone'
		}).forEach(([key, val]) => {
			result[val] = new RegExp(key, 'i').test(ua);
		});
		return result;
	},
	/* ===== Object ===== */
	$deepClone(obj) {
		return JSON.parse(JSON.stringify(obj));
	},
	/* ===== Array ===== */
	$uniqueArray(arr) {
		return Array.from(new Set(arr));
	},
	$inArray(item, arr) {
		return !!~arr.indexOf(item);
	},
	$spliceArray(arr, start, len, rpl) {
		arr.splice(start, len, rpl);
		return arr;
	},
	/* ===== Date ===== */
	$dateFormat(date, fmt = 'yyyy-MM-dd hh:mm:ss') {
		return fmt.replace(/(^|.*[^y])(yyyy)([^y].*|$)/, `$1${date.getFullYear()}$3`)
			.replace(/(^|.*[^y])(yy)([^y].*|$)/, `$1${date.getFullYear().toString().slice(-2)}$3`)
			.replace(/(^|.*[^M])(MM)([^M].*|$)/, `$1${(date.getMonth() + 1).toString().padStart(2, '0')}$3`)
			.replace(/(^|.*[^M])(M)([^M].*|$)/, `$1${date.getMonth() + 1}$3`)
			.replace(/(^|.*[^d])(dd)([^d].*|$)/, `$1${date.getDate().toString().padStart(2, '0')}$3`)
			.replace(/(^|.*[^d])(d)([^d].*|$)/, `$1${date.getDate()}$3`)
			.replace(/(^|.*[^h])(hh)([^h].*|$)/, `$1${date.getHours().toString().padStart(2, '0')}$3`)
			.replace(/(^|.*[^h])(h)([^h].*|$)/, `$1${date.getHours()}$3`)
			.replace(/(^|.*[^m])(mm)([^m].*|$)/, `$1${date.getMinutes().toString().padStart(2, '0')}$3`)
			.replace(/(^|.*[^m])(m)([^m].*|$)/, `$1${date.getMinutes()}$3`)
			.replace(/(^|.*[^s])(ss)([^s].*|$)/, `$1${date.getSeconds().toString().padStart(2, '0')}$3`)
			.replace(/(^|.*[^s])(s)([^s].*|$)/, `$1${date.getSeconds()}$3`);
	},
	/* ===== Function ===== */
	$makeGenerator(func) {
		let gen = function* (ipt) {
			for (; ;) {
				ipt = yield func(ipt);
			}
		};
		let n = 0;
		let g;
		let init = function (arg) {
			g = gen(arg);
		};
		let cnt = 'count';
		return {
			next(arg) {
				if (n++) {
					return Object.assign({
						[cnt]: n
					}, g.next(arg));
				}
				init(arg);
				return Object.assign({
					[cnt]: n
				}, g.next());
			},
			last(arg) {
				let res = Object.assign({}, this.next(arg), {
					'done': true
				});
				g.return();
				return res;
			},
			return(arg) {
				n++ || init(arg);
				return Object.assign({
					[cnt]: n
				}, g.return(arg));
			},
			throw(err) {
				n++ || init(err);
				return Object.assign({
					[cnt]: n
				}, g.throw(err));
			}
		};
	}
};

export default {
	install(Vue, options) {
		Object.entries(extensionMap).forEach(([key, val]) => Vue[key] = Vue.prototype[key] = val);
	}
};
